package cus.area.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cus.area.util.constants.AreaData;
import cus.area.util.entity.AreaEnum;
import cus.area.util.entity.ParseAreaResult;
import cus.area.util.param.FindIndexFromAddress;
import cus.area.util.param.ParseByTargetParam;
import cus.area.util.param.ParseChildByParentParam;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * 地址解析2,重构了ParseArea类,实现逻辑和ParseArea一致
 */
public class ParseArea2 {
    private static Map<String, String> provinceShort = new LinkedHashMap<>();

    private static Map<String, String> cityShort = new LinkedHashMap<>();

    private static Map<String, String> countyShort = new LinkedHashMap<>();

    static {
        for (Map.Entry<String, String> entry : AreaData.PROVINCES.entrySet()) {
            String result = entry.getValue();
            for (String key : AreaData.provinceKeys) {
                result = result.replace(key, "");
            }
            provinceShort.put(entry.getKey(), result);
        }

        for (Map.Entry<String, String> entry : AreaData.CITIES.entrySet()) {
            String result = entry.getValue();
            if (result.length() > 2) {
                for (String key : AreaData.cityKeys) {
                    result = result.replace(key, "");
                }
                cityShort.put(entry.getKey(), result);
            }
        }

        for (Map.Entry<String, String> entry : AreaData.COUNTIES.entrySet()) {
            String result = entry.getValue();
            if ("雨花台区".equals(result)) {
                result = "雨花区";
            }
            if (result.length() > 2) {
                for (String key : AreaData.countyKeys) {
                    if (result.indexOf(key) > 0) {
                        result = result.replace(key, "");
                    }
                }
                countyShort.put(entry.getKey(), result);
            }
        }
    }

    public static List<ParseAreaResult> parse(String address, boolean parseAll) {
        List<ParseAreaResult> list = new ArrayList<>();
//        list.addAll(0, parseByProvince(address)));
        list.addAll(0, parseByTarget(address,AreaData.PROVINCES,provinceShort,provinceFunction()));
        if (parseAll || CollectionUtil.isEmpty(list) || !list.get(0).getParse()) {
//            list.addAll(0,  parseByCity(address));
              list.addAll(0,  parseByTarget(address,AreaData.CITIES,cityShort,cityFunction()));
            if (parseAll || CollectionUtil.isEmpty(list) || !list.get(0).getParse()) {
//                list.addAll(0, parseByCounty(address));
                list.addAll(0,parseByTarget(address,AreaData.COUNTIES,countyShort,countyFunction()));
            }
        }

        // 可信度排序
        list.sort((a, b) -> {
            int aNameLength = a.getName() == null ? -1 : a.getName().length();
            int bNameLength = b.getName() == null ? -1 : b.getName().length();
            return a.getParse() && !b.getParse() ? -1 : !a.getParse() && b.getParse() ? 1 : aNameLength > bNameLength ? 1 : aNameLength < bNameLength ? -1 : 0;
        });

        return list;
    }

    /**
     * 重构方法,类似模板方法,具体解析方法由countyFunction,cityFunction,provinceFunction提供
     * @param addressBase
     * @return
     */
    private static List<ParseAreaResult> parseByTarget(String addressBase, Map<String,String> targetMap, Map<String,String> shortMap, Function<ParseByTargetParam,Boolean> function) {
        List<ParseAreaResult> results = new ArrayList<>();
        ParseAreaResult result = new ParseAreaResult();
        String address = addressBase;
        for (Map.Entry<String, String> entry : targetMap.entrySet()) {
            FindIndexFromAddress findIndexFromAddress = new FindIndexFromAddress(address, entry, shortMap).invoke();
            if (findIndexFromAddress.index > -1) {
                ParseByTargetParam param = new ParseByTargetParam();
                param.setResult(result);
                param.setAddress(address);
                param.setResults(results);
                param.setFindIndexFromAddress(findIndexFromAddress);
                //functio提供具体解析方式,由于其中需要参数较多,因此封装ParseByTargetParam对象作为参数对象传递
                //返回结果用于判断是否跳过,只要result.parse为true就跳过
                if(function.apply(param)){
                    break;
                }
            }
        }
        return results;
    }

    /**
     * county解析方式
     * @return
     */
    private static Function<ParseByTargetParam,Boolean> countyFunction(){
        return (params) -> {
            FindIndexFromAddress findIndexFromAddress = params.getFindIndexFromAddress();
            ParseAreaResult result = params.getResult();
            List<ParseAreaResult> results = params.getResults();
            String address = params.getAddress();
            int index = findIndexFromAddress.index;
            String countyCode = findIndexFromAddress.code;
            String countyName = findIndexFromAddress.name;
            int countyLength = findIndexFromAddress.nameLength;

            result.setCountyCode(countyCode);
            result.setCounty(countyName);
            result.setCode(countyCode);

            String provinceCode = countyCode.substring(0, 2) + "0000";
            String cityCode = countyCode.substring(0, 4) + "00";
            String province = AreaData.PROVINCES.get(provinceCode);
            String city = AreaData.CITIES.get(cityCode);
            result.setProvinceCode(provinceCode);
            result.setProvince(province);
            result.setCityCode(cityCode);
            result.setCity(city);

            String leftAddress = address.substring(0, index);
            String _provinceName = "", _cityName = "";
            if (StrUtil.isNotEmpty(leftAddress)) {
                _provinceName = province;
                int _index = leftAddress.indexOf(_provinceName);
                if (_index == -1) {
                    _provinceName = provinceShort.get(countyCode.substring(0, 2) + "0000");
                    _index = leftAddress.indexOf(_provinceName);
                    if (_index == -1) {
                        _provinceName = "";
                    }
                }

                if (StrUtil.isNotEmpty(_provinceName)) {
                    leftAddress = leftAddress.replaceAll(_provinceName, "");
                }

                _cityName = city;
                _index = leftAddress.indexOf(_cityName);
                if (_index == -1) {
                    _cityName = cityShort.get(countyCode.substring(0, 4) + "00");
                    _index = leftAddress.indexOf(_cityName);
                    if (_index == -1) {
                        _cityName = "";
                    }
                }
                if (StrUtil.isNotEmpty(_cityName)) {
                    leftAddress = leftAddress.replaceAll(_cityName, "");
                }

                if (StrUtil.isNotEmpty(leftAddress)) {
                    result.setName(leftAddress);
                }
            }

            address = address.substring(index + countyLength);

            //判断是否解析完成,通过county解析,找到省,市,说明反向解析到,可以确定解析完成
            boolean parsed = StrUtil.isNotEmpty(_provinceName) || StrUtil.isNotEmpty(_cityName);
            return processParseResult(result, results, address, parsed);
        };
    }

    /**
     * 处理解析结果
     * @param result
     * @param results
     * @param address
     * @param parsed
     * @return
     */
    private static Boolean processParseResult(ParseAreaResult result, List<ParseAreaResult> results, String address, boolean parsed) {
        if (parsed) {
            result.setParse(true);
            result.setDetails(address.trim());
            results.add(0, result);
            return true;
        } else {
            //如果没有识别到地区 缓存本次结果，并重置数据
            ParseAreaResult newResult = new ParseAreaResult();
            BeanUtil.copyProperties(result, newResult);
            newResult.setDetails(address.trim());
            results.add(0, newResult);
            result.clean();
            return false;
        }
    }

    /**
     * 城市解析方式
     * @return
     */
    private static Function<ParseByTargetParam,Boolean> cityFunction(){
        return (params) -> {
            FindIndexFromAddress findIndexFromAddress = params.getFindIndexFromAddress();
            ParseAreaResult result = params.getResult();
            List<ParseAreaResult> results = params.getResults();
            String address = params.getAddress();
            int index = findIndexFromAddress.index;
            String cityCode = findIndexFromAddress.code;
            String cityName = findIndexFromAddress.name;
            int cityLength = findIndexFromAddress.nameLength;

            result.setCode(cityCode);
            result.setCity(cityName);
            result.setCityCode(cityCode);

            String provinceCode = cityCode.substring(0, 2) + "0000";
            String province = AreaData.PROVINCES.get(provinceCode);
            result.setProvinceCode(provinceCode);
            result.setProvince(province);

            String leftAddress = address.substring(0, index);
            String _provinceName = "";
            if (StrUtil.isNotEmpty(leftAddress)) {
                _provinceName = province;
                int _index = leftAddress.indexOf(_provinceName);
                if (_index == -1) {
                    _provinceName = provinceShort.get(cityCode.substring(0, 2) + "0000");
                    _index = leftAddress.indexOf(_provinceName);
                    if (_index == -1) {
                        _provinceName = "";
                    }
                }
                if (StrUtil.isNotEmpty(_provinceName)) {
                    leftAddress = leftAddress.replace(_provinceName, "");
                }
                if (StrUtil.isNotEmpty(leftAddress)) {
                    result.setName(leftAddress);
                }
            }

            address = address.substring(index + cityLength);
            address = parseChildByParent(address, result, buildCountyCityParam(result));

            //判断是否解析完成,通过city解析,找到省,地区,说明反向解析到省,或者向下解析到地区,可以确定解析完成
            boolean parsed = StrUtil.isNotEmpty(_provinceName) || StrUtil.isNotEmpty(result.getCounty());
            return processParseResult(result, results, address, parsed);
        };
    }

    /**
     * 省解析方式
     * @return
     */
    private static Function<ParseByTargetParam,Boolean> provinceFunction(){
        return (params) -> {
            FindIndexFromAddress findIndexFromAddress = params.getFindIndexFromAddress();
            ParseAreaResult result = params.getResult();
            List<ParseAreaResult> results = params.getResults();
            String address = params.getAddress();
            int index = findIndexFromAddress.index;
            String provinceCode = findIndexFromAddress.code;
            String province = findIndexFromAddress.name;
            int provinceLength = findIndexFromAddress.nameLength;
            String shortProvince = findIndexFromAddress.shortName;

            if (index > 0) {
                result.setName(address.substring(0, index).trim());
                address = address.substring(index).trim();
            }

            result.setCode(provinceCode);
            result.setProvince(province);
            result.setProvinceCode(provinceCode);

            String _address = address.substring(provinceLength);
            //第一个字符不为市,或者还有省份,如果为 四川省市辖区 这种不会进入
            if (!"市".equals(_address.charAt(0)) || _address.indexOf(province) > -1) {
                address = _address;
            }

            //如果是用短名匹配的 要替换省关键字
            if (StrUtil.isNotEmpty(shortProvince)) {
                address = replaceKeys(address, AreaData.provinceKeys, (finalAddress, key) -> finalAddress.indexOf(key) == 0);
            }

            String __address = parseChildByParent(address, result, buildCityParam(result));
            if (StrUtil.isEmpty(result.getCity())) {
                __address = parseChildByParent(address, result, buildCountyProvinceParam(result));
            }

            //判断是否解析完成,通过province解析,找到城市,说明向下解析到数据,可以确定解析完成
            boolean parsed = StrUtil.isNotEmpty(result.getCity());
            address = parsed ? __address : address;
            return processParseResult(result, results, address , parsed);
        };
    }

    /**
     * 通过条件解析地址,该方法为重构方法,类似于模板模式,通过param来控制如何处理解析的地址
     * 因为parseCityByProvince,parseCountyByProvince,parseCountyByCity的处理方法类似,重构出该方法
     *
     * @param address
     * @param result
     * @param param
     * @return
     */
    private static String parseChildByParent(String address, ParseAreaResult result, ParseChildByParentParam param) {
        //要获取的target集合
        AreaEnum target = param.getAreaEnum();
        //地址缩写,可能为省,市,地区
        Map<String, String> shortMaps = param.getShortMap();
        //当找到对应的地址时,应该做的操作
        BiConsumer<String, String> biConsumer = param.getBiConsumer();
        //为地址缩写时,需要截取的key集合
        Set<String> keys = param.getKeys();
        //为地址缩写时,判断是否需要截取的方法,第一个参数为address,第二个参数为遍历到的key
        //由于截取方法是递归的,第一个参数会变化,为当前处理到的address字符串
        BiFunction<String, String, Boolean> biFunction = param.getBiFunction();
        //是否进行下一步解析,以此为判断条件,有nextParam就进行下一步解析
        ParseChildByParentParam nextParam = param.getNextParam();
        Integer maxIndex = param.getMaxIndex();

        //通过code查询target的集合,例如 target为市,code为330100,则查询浙江省下的所有市
        Map<String, String> targets = AreaUtils.getTargetsByCode(target, result.getCode());
        for (Map.Entry<String, String> entry : targets.entrySet()) {
            FindIndexFromAddress findIndexFromAddress = new FindIndexFromAddress(address,entry,shortMaps).invoke();
            String code = findIndexFromAddress.code;
            String name = findIndexFromAddress.name;
            String shortName = findIndexFromAddress.shortName;
            int nameLength = findIndexFromAddress.nameLength;
            int index = findIndexFromAddress.index;

            if (index > -1 && index < maxIndex) {
                biConsumer.accept(code, name);
                //截取新的地址
                address = address.substring(index + nameLength);
                //如果是用短名匹配的 要替换关键字
                if (StrUtil.isNotEmpty(shortName)) {
                    //递归替换掉keys开头的字符,biFunction判断是否替换
                    address = replaceKeys(address, keys, biFunction);
                }

                //是否进行下一步解析
                if (nextParam != null) {
                    address = parseChildByParent(address, result, nextParam);
                }
                break;
            }
        }
        return address;
    }

    /**
     * 建立查找city的解析参数,用于向parseChildByParent
     * @param result
     * @return
     */
    private static ParseChildByParentParam buildCityParam(ParseAreaResult result) {
        ParseChildByParentParam countyParam = buildCountyCityParam(result);
        BiFunction<String, String, Boolean> biFunction = (finalAddress, key) -> finalAddress.indexOf(key) == 0 && !StrUtil.equals(key, "市");
        BiConsumer<String, String> biConsumer = (c, n) -> {
            result.setCode(c);
            result.setCity(n);
            result.setCityCode(c);
        };
        return new ParseChildByParentParam.ParseChildByParentParamBuilder().areaEnum(AreaEnum.CITY).keys(AreaData.cityKeys).shortMap(cityShort).biFunction(biFunction).biConsumer(biConsumer).nextParam(countyParam).build();
    }

    /**
     * 建立通过city查找county的解析参数,用于向parseChildByParent
     * @param result
     * @return
     */
    private static ParseChildByParentParam buildCountyCityParam(ParseAreaResult result) {
        BiConsumer<String, String> biConsumer = (c, n) -> {
            String countyCode;
            if (c.contains("-")) {
                countyCode = c.split("-")[0];
            }else{
                countyCode = c;
            }
            result.setCode(countyCode);
            result.setCounty(n);
            result.setCountyCode(countyCode);
        };
        return buildCountyParam(result, biConsumer);
    }

    /**
     * 建立通过province查找county的解析参数,用于向parseChildByParent
     * @param result
     * @return
     */
    private static ParseChildByParentParam buildCountyProvinceParam(ParseAreaResult result) {
        BiConsumer<String, String> biConsumer = (c, n) -> {
            String countyCode;
            if (c.contains("-")) {
                countyCode = c.split("-")[0];
            }else{
                countyCode = c;
            }
            result.setCode(countyCode);
            result.setCounty(n);
            result.setCountyCode(countyCode);

            String cityCode = countyCode.substring(0, 4) + "00";
            String cityName = AreaData.CITIES.get(cityCode);
            result.setCity(cityName);
            result.setCityCode(cityCode);
        };
        return buildCountyParam(result, biConsumer);
    }

    /**
     * 建立查找county的参数,是buildCountyCityParam,buildCountyProvinceParam的基类方法
     * @param result
     * @param biConsumer
     * @return
     */
    private static ParseChildByParentParam buildCountyParam(ParseAreaResult result, BiConsumer<String, String> biConsumer) {
        BiFunction<String, String, Boolean> biFunction = (finalAddress, key) -> finalAddress.indexOf(key) == 0;
        return new ParseChildByParentParam.ParseChildByParentParamBuilder().areaEnum(AreaEnum.COUNTY).keys(AreaData.countyKeys).shortMap(countyShort).biFunction(biFunction).biConsumer(biConsumer).build();
    }

    /**
     * 递归替换掉address中以keys开头的字符
     *
     * @param address
     * @param keys
     * @param biFunction
     * @return
     */
    private static String replaceKeys(String address, Set<String> keys, BiFunction<String, String, Boolean> biFunction) {
        String finalAddress = address;
        String key = keys.stream().filter(k -> {
            return biFunction.apply(finalAddress, k);
        }).findFirst().orElseGet(String::new);
        if (StrUtil.isNotEmpty(key)) {
            address = address.substring(key.length());
            //递归查找
            address = replaceKeys(address, keys, biFunction);
        }
        return address;
    }
}
